package fast.calc.druid;

import fast.calc.AttackType;
import fast.calc.AttacksBlock;
import fast.calc.Buffs;
import fast.calc.Fast;
import fast.calc.Stats;
import fast.calc.WeaponStats;
import fast.data.BaseStats;
import fast.data.Spec;
import fast.data.Stat;

import java.util.HashMap;
import java.util.Map;

public class FastDruid extends Fast {

	private AttacksDruid attacks;
	
	private Map<String, Double> dps = new HashMap<String, Double>();
	private Map<String, Double> dpsOnlyCrit = new HashMap<String, Double>();
	
	private double mh_swings_per_sec;
	private double total_hits;
	private double omen_of_clarity_procs_per_sec;
	
	private static final double reaction_time = 0.5;
	//private static final double PRECISION = Math.pow(10, -8);
	
	public FastDruid(BaseStats bs){
		stats = new Stats();
		stats.setBuffs(new Buffs()); // raid buffs
		stats.setSpec(bs.getSpec());
		stats.add(bs);
		
		stats.setMh(new WeaponStats(bs.getMH().dps, bs.getMH().speed, bs.getMH().type));
		if(bs.getOH() != null)
			stats.setOh(new WeaponStats(bs.getOH().dps, bs.getOH().speed, bs.getOH().type));
		else
			stats.setOh(new WeaponStats());
		
		stats.setGearBuffs(bs.getGearBuffs());
		if(!bs.getMH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("mh_"+bs.getMH().getEnchant());
		if(bs.getOH() != null && !bs.getOH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("oh_"+bs.getOH().getEnchant());

		stats.setSetItems(bs.getSetItems());
		stats.setTalents(bs.getTalents());
		stats.setGlyphs(bs.getGlyphs());
		
//		for(String sbs : stats.getGearBuffs().getSetItems()){
//			System.out.println("setitems " + sbs);
//		}
//		for(String t : bs.getTalents()){
//			System.out.println("talents " + t);
//		}
//		for(String gl : bs.getGlyphs()){
//			System.out.println("||glyph " + gl);
//		}

		calc();
	}
	
	public FastDruid(){
		stats = new Stats();
	}
	
	private void calc(){

		attacks = new AttacksDruid(this);
		dps1 = get_dps();
		dpsBreakdown = new HashMap<String,Double>(dps);
		for(String attack : dps.keySet()){
			System.out.println(attack + " " + dps.get(attack) + " (" + ((double)Math.round(dps.get(attack)/dps1*10000))/100 + "%)");
		}
		System.out.println("DPS: " + dps1);
		
		// calc ep
		calc_ep();
	}
	
	public double get_dps(){
		
		aps.clear();
		dps.clear();
		dpsOnlyCrit.clear();
		total_hits = 0.0;
		
		if(stats.getSpec() == Spec.Feral)
			calc_dps_feral();
		else 
			new Exception("Spec not implemented: " + stats.getSpec());
		
		for(String attack : aps.keySet()){
			calc_dps_contribution(attacks.get_dmg(attack),attacks.get_crit(attack));
		}
		double dps1 = 0.0;
		for(String attack : dps.keySet()){
			dps1 += dps.get(attack);
		}
		return Math.max(dps1, 1.0);
	}
	
	@SuppressWarnings("unused")
	private void calc_dps_feral(){
		double shred_ec = (40-berserker_energy_reduction(40)) *(0.8 + 0.2/stats.getMhStyleHitChance());
		double rake_ec = (35-berserker_energy_reduction(35)) *(0.8 + 0.2/stats.getMhStyleHitChance());
		double rip_ec = (30-berserker_energy_reduction(30)) *(0.8 + 0.2/stats.getMhStyleHitChance());
		double fb_ec = (25-berserker_energy_reduction(25)) *(0.8 + 0.2/stats.getMhStyleHitChance());
		double sr_ec = 25-berserker_energy_reduction(25);
		
		double finisher_cps = 5;
		double sr_duration = 12 + 6 * finisher_cps;
		
		while(true){
			double rake_energy_return = (shred_ec-rake_ec)/15;
			//System.out.println(rake_energy_return);
			double energy_regen = get_energy_regen() + rake_energy_return;
			
			
			double extra_cps_through_omen_of_clarity = omen_of_clarity_procs_per_sec;
			
			double shred_per_finisher = finisher_cps / 1;
			double energy_cost_builder = shred_per_finisher * shred_ec;
			double buildtime = energy_cost_builder / energy_regen;
			double buildtime_rip = buildtime + (rip_ec-finisher_cps*4) / energy_regen;
			double buildtime_sr = buildtime + (sr_ec-finisher_cps*4) / energy_regen;
			double buildtime_fb = buildtime + (fb_ec-finisher_cps*4) / energy_regen;
			
			double rip_per_sr = sr_duration / 22.;
			
			double time_left_for_ferocious_bite = sr_duration - buildtime_sr - buildtime_rip * rip_per_sr;
			double fb_per_sr = time_left_for_ferocious_bite / buildtime_fb;
			
			
			double shred_per_sec = shred_per_finisher * (1 + rip_per_sr + fb_per_sr) / sr_duration;
			double fb_per_sec = fb_per_sr / sr_duration;
			
			this.white_attacks();
			
			double total_mh_hits = aps.get("mh") + aps.get("rake") + aps.get("shred");
			total_mh_hits += aps.get("ferocious_bite");
			
			if(Math.abs(total_hits - (total_mh_hits)) < PRECISION)
				break;
			
			total_hits = total_mh_hits;
			
			this.procs(total_mh_hits,0.0, 0.0);
		
		}
		
	}
	
	private void white_attacks(){
		mh_swings_per_sec = get_total_speed()/1.0; // fixed attack speed for cats

		double mh_hits_per_sec = this.stats.get2HandHitChance() * mh_swings_per_sec;
		
		omen_of_clarity_procs_per_sec = mh_hits_per_sec * 0.04;
		
		aps.put("mh", mh_hits_per_sec);
	}
	
	private double berserker_energy_reduction(double ec){
		double berserk_uptime = 15. / (180. + reaction_time);
		return ec / 2 * berserk_uptime;
	}

	private double get_energy_regen(){
		double base_regen = 10;

		double regen = base_regen * (1.0 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
		if(stats.getSpec() == Spec.Feral)
			regen += 60 * 6 / (30+reaction_time);
		if(stats.getGearBuffs().has("berserking"))
			regen += base_regen * 0.2 * 10 / 180.;
		if(stats.getGearBuffs().has("arcane_torrent"))
			regen += 15 * 1. / 120;
		// spec things

		return regen;
	}
	
	protected double get_total_speed(){
		double speed = 1 * (1 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
		if(stats.getBuffs().hasBuff("attackspeed_buff"))
			speed *= 1.1;
		if(stats.getGearBuffs().has("berserking"))
			speed *= 1 + .2 * 10. / 180;
		if(stats.getGearBuffs().has("time_is_money"))
            speed *= 1.01;
		
		speed *= (1 + 0.3 * 40 / FIGHT_LENGTH);
		return speed;
	}
	
	private double get_eff_dmg(){
		return get_eff_dmg(Stats.BOSS_ARMOR);
	}
	//
	private double get_eff_dmg(double armor){
		double mod = 1;
		if(stats.getBuffs().hasBuff("armor_debuff"))
			mod -= 0.12;
		double boss_armor = armor * mod;
		double param = 85 * 2167.5 - 158167.5; // lvl 85 player and target 88
		param = 90 * 4037.5 - 317117.5; // LEVEL_90 player and target 93
		return param / (boss_armor + param);
	}

	private void calc_dps_contribution(AttacksBlock ab,double crit_chance){
		double hitDamage = ab.getHitDamage();
		double critDamage = ab.getCritDamage();
		
		double dmgMod = 1.0;
		if(ab.getType() == AttackType.physical){
			if(stats.getBuffs().hasBuff("physicaldmg_debuff"))
				dmgMod *= 1.04;
			dmgMod *= get_eff_dmg();
		}
		if(ab.getType() == AttackType.magical){
			if(stats.getBuffs().hasBuff("spelldmg_debuff"))
				dmgMod *= 1.05;
		}
		if(stats.getSpec() == Spec.Feral){
			if(ab.getType() == AttackType.physical){
				dmgMod *= 1.3; // savage roar
				dmgMod *= 1 + 0.15 * 6 /  (30 + reaction_time); // tigers fury
			}
			if(ab.getType() == AttackType.bleed){
				dmgMod *= (1 + this.stats.getBuffed(Stat.mst) * 0.031);
			}
		}
		hitDamage *= dmgMod;
		critDamage *= dmgMod;
		
		if(ab.getAttack().equals("mh")){
			double hit = hitDamage * (stats.get2HandHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit + glancing) * mh_swings_per_sec);
			dpsOnlyCrit.put(ab.getAttack(), crit * mh_swings_per_sec);
		}else{
			double hit = hitDamage * ( 1 - crit_chance );
			double crit = critDamage * crit_chance;
			dps.put(ab.getAttack(), (hit + crit) * aps.get(ab.getAttack()));
			dpsOnlyCrit.put(ab.getAttack(), crit * aps.get(ab.getAttack()));
		}
	}

	@Override
	public Map<String, Double> getEP(){
		return ep;
	}
}
